/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or <http://code.google.com/p/space-chronicles/>.
 */
package space.app.meshview.state;

import java.awt.event.MouseEvent;
import javax.media.opengl.awt.GLCanvas;
import jinngine.math.Quaternion;
import jinngine.math.Vector3;

/**
 * Inspired by blender 3D manipulator implementation http://www.blender.org/
 * @author Pierre
 */
abstract class AbstractRotateState extends AbstractState {

    static final double TRACKBALL_SIZE = 1.1;
    // Quaternation value at mouse press time
    // Trackvector value at mouse press time
    final Vector3 oldTrack;
    final Quaternion light;
    final MeshCamera camera;

    public AbstractRotateState(MeshCamera camera, Quaternion light, MouseEvent e) {
        final GLCanvas canvas = (GLCanvas) e.getSource();
        this.oldTrack = calctrackballvec(canvas.getWidth(), canvas.getHeight(),
                e.getX(), e.getY());
        this.camera = camera;
        this.light = light;
    }

    private Vector3 calctrackballvec(int w, int h, int mx, int my) {
        final double M_SQRT2 = 1.41421356237309504880;
        final double M_SQRT1_2 = 0.707106781186547524401;

        final double radius = TRACKBALL_SIZE;

        /* normalize x and y */
        double x = w / 2 - mx;
        x /= w / 4.;
        double y = my - h / 2;
        y /= w / 4.;

        double z;
        double d = Math.sqrt(x * x + y * y);
        if (d < radius * M_SQRT1_2) {
            /* Inside sphere */
            z = Math.sqrt(radius * radius - d * d);
        } else {
            /* On hyperbola */
            double t = radius / M_SQRT2;
            z = t * t / d;
        }

        return new Vector3(x, y, -z);	/* yah yah! */
    }

    @Override
    public AbstractState mouseDragged(MouseEvent e) {
        final GLCanvas canvas = (GLCanvas) e.getSource();
        final Vector3 newTrack = calctrackballvec(canvas.getWidth(), canvas.getHeight(), e.getX(), e.getY());
        final Vector3 dvec = newTrack.sub(oldTrack);
        double si = dvec.norm();
        si /= (2.0 * TRACKBALL_SIZE);
        /* Allow for rotation beyond the interval
         * [-pi, pi] */
        while (si > 1.0) {
            si -= 2.0;
        }

        /* This relation is used instead of
         * phi = asin(si) so that the angle
         * of rotation is linearly proportional
         * to the distance that the mouse is
         * dragged. */
        final double phi = si * Math.PI /*/ 2.0*/;

        final Vector3 cro = oldTrack.cross(newTrack).normalize();
        final Quaternion q1 = Quaternion.rotation(phi, cro);
        applyTransformation(q1);
        redraw(e);

        return this;
    }

    /**
     * Apply the comulated transformation from the state creation instent up to
     * now to the malipulated object
     * @param q
     */
    abstract void applyTransformation(Quaternion q);

    @Override
    public AbstractState mouseReleased(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON1) {
            return new DefaultState(camera, light);
        }
        return this;
    }
}
